package com.zyajac.springBootDemo.commonUtils.EncryptionUtils;


import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Hex;

import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Objects;
import java.util.Random;

@Slf4j
public class Md5Utils {


    /**
      * MD5 加密方式1
      * @author LZG
      * @date  2019/8/9
      * @param  str 源
      * @return 加密后的值
      **/
    private static String md5Hex(String str){
        try{
            MessageDigest md = MessageDigest.getInstance("MD5");
            byte[] digest = md.digest(str.getBytes());


            StringBuilder stringBuilder = new StringBuilder();
            for (int i = 0; i < digest.length; i++) {
                //byte 是先转位int 在进行 & 运算
                //byte 在转int的时候  1111 1000  ->>      11111111 11111111 11111111 11111000
                //11111111 11111111 11111111 11111000  ->> 00000000 00000000 00000000 11111000
                //这个是为了防止转换的额时候出现的精度问题
                int var = digest[i] & 0xff ;
                if(var < 16 ){
                    stringBuilder.append("0");
                }
                stringBuilder.append(Integer.toHexString(var));
            }

            return stringBuilder.toString();
        }catch (NoSuchAlgorithmException e){
            log.error( "Md5Util Encoder Error"+e.getMessage());
            return "";
        }
    }

    /**
     * MD5 加密方式2
     * @author LZG
     * @date  2019/8/9
     * @param  str 源
     * @return 加密后的值
     **/
    private static String md5Hex2(String str){
        char[] hexDigits = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
        try{
            byte[] bytes = str.getBytes(StandardCharsets.UTF_8);
            MessageDigest md = MessageDigest.getInstance("MD5");
            md.update(bytes);
            byte[] digest = md.digest(str.getBytes());
            int mdLength = digest.length;
            char[] tempStr = new char[mdLength * 2];
            for (int i = 0 , k = 0; i < mdLength; i++) {
                tempStr[k++] = hexDigits[digest[i] >>> 4 & 15];
                tempStr[k++] = hexDigits[digest[i] & 15];
            }
            return new String(tempStr);
        } catch (NoSuchAlgorithmException e){
            log.error( "Md5Util Encoder Error"+e.getMessage());
            return "";
        }
    }

    /**
     * MD5 加密方式3
     * @author LZG
     * @date  2019/8/9
     * @param  str 源
     * @return 加密后的值
     **/
    private static String md5Hex3(String str) {
        try {

            MessageDigest md = MessageDigest.getInstance("MD5");
            byte[] digest = md.digest(str.getBytes());
            return new String(new Hex().encode(digest));
        } catch (Exception e) {
            log.error(e.getMessage());
            return "";
        }
    }

    /**
     * MD5 撒盐 加密
     * @author LZG
     * @date  2019/8/9
     * @param  str 源
     * @return 加密后的值
     **/
    private static String md5WithSalt(String str){
        //生成16位的随机数
        StringBuilder salt = new StringBuilder(16);
        Random random  = new Random();
        salt.append(random.nextInt(99999999)).append(random.nextInt(99999999));

        //补足16位
        while(salt.length() != 16){
            int len = salt.length();
            if(len < 16) salt.append("0");
            else salt.deleteCharAt(len);
        }

        String encryptCode;
        if((encryptCode = md5Hex(str + salt)).length() != 32){
            log.error("MD5 加密错误 StringIndexOutOfBoundsException ");
            return null;
        }


        /*混合结果数组*/
        char[] res = new char[48];
        for (int i = 0; i < res.length ; i +=3) {
            res[i] = encryptCode.charAt(i / 3 * 2 );
            res[i + 1] = salt.toString().charAt(i / 3);
            res[i + 2] = encryptCode.charAt(i / 3 * 2 + 1);
        }

        //混入盐 和 加密数据
        return String.valueOf(res);
    }

    /**
     * 检验 MD5加密
     * @author LZG
     * @date  2019/8/9
     * @param  MD5Src 加密后源  src 需要校验的值
     * @return 加密后的值
     **/
    public static boolean verifyMD5WithSalt(String src, String MD5Src) {
        if(MD5Src.length() != 48) return false;
        char[] cs1 = new char[32];
        char[] salt = new char[16];
        try{
            for (int i = 0; i < 48; i += 3) {
                cs1[i / 3 * 2] = MD5Src.charAt(i);
                cs1[i / 3 * 2 + 1] = MD5Src.charAt(i + 2);
                salt[i / 3] = MD5Src.charAt(i + 1);
            }
        }catch (StringIndexOutOfBoundsException e){
            log.error(e.getMessage());
        }

        return md5Hex(src + new String(salt)).equals(String.valueOf(cs1));
    }


    /**
      * 加密
      * @author LZG
      * @date  2019/8/10
      * @param  src 加密源
      * @return 加密后的字符
      **/
    public static  String encode(String src){
        return md5WithSalt(src);
    }



    /*测试*/
    public static void main(String[] args) {
        String src = "8080";
        System.out.println(md5Hex(src));
        System.out.println(md5Hex2(src));
        System.out.println(md5Hex3(src));
        System.out.println(md5WithSalt(src));
        System.out.println(verifyMD5WithSalt(src , Objects.requireNonNull(md5WithSalt(src))));
    }
}
